package com.tegtech.starter.redis.service;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import lombok.extern.slf4j.Slf4j;

/**
 * Redis工具类
 *
 * @Author Jun
 * @date 2019年5月17日 上午10:25:20
 */
@Slf4j
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public final class RedisCache {

	@Autowired
	private RedisTemplate redisTemplate;

	// =============================common============================

	/**
	 * 指定缓存失效时间
	 *
	 * @param key  键
	 * @param time 时间(秒)
	 * @return
	 */
	public void expire(String key, long time) {
		if (time > 0) {
			redisTemplate.expire(key, time, TimeUnit.SECONDS);
		}
	}

	/**
	 * 根据key 获取过期时间
	 *
	 * @param key 键 不能为null
	 * @return 时间(秒) 返回0代表为永久有效
	 */
	public long getExpire(String key) {
		return redisTemplate.getExpire(key, TimeUnit.SECONDS);
	}

	/**
	 * 判断key是否存在
	 *
	 * @param key 键
	 * @return true 存在 false不存在
	 */
	public boolean hasKey(String key) {
		return redisTemplate.hasKey(key);
	}

	/**
	 * 删除缓存
	 *
	 * @param key 可以传一个值 或多个
	 */
	public void del(String... key) {
		if (key != null && key.length > 0) {
			if (key.length == 1) {
				redisTemplate.delete(key[0]);
			} else {
				redisTemplate.delete(CollectionUtils.arrayToList(key));
			}
		}
	}
	
	/**
	 * 删除集合对象
	 *
	 * @param collection 多个对象
	 * @return
	 */
	public long del(final Collection collection) {
		return redisTemplate.delete(collection);
	}

	// ============================普通对象=============================

	/**
	 * 获得缓存的基本对象
	 *
	 * @param key 缓存键值
	 * @return 缓存键值对应的数据
	 */
	public <T> T get(String key) {
		ValueOperations<String, T> operation = redisTemplate.opsForValue();
		return operation.get(key);
	}
	
	/**
	 * 缓存基本的对象，Integer、String、实体类等
	 *
	 * @param key      缓存的键值
	 * @param value    缓存的值
	 * @param timeout  时间(秒) time要大于0 如果time小于等于0 将设置无限期
	 * @return 	缓存的对象
	 */
	public <T> void set(String key, T value, long timeout) {
		ValueOperations<String, T> operation = redisTemplate.opsForValue();
		if(timeout > 0) {
			operation.set(key, value, timeout, TimeUnit.SECONDS);
		}else {
			operation.set(key, value);
		}
	}
	
	/**
	 * 如果键存在则设置新值，不存在则不做任何操作
	 * @param <T>
	 * @param key
	 * @param value
	 * @param timeout
	 * @return	true 表示key存在且已设新值； false 表示key不存在未做任何操作
	 */
	public <T> boolean setIfPresent(String key, T value, long timeout) {
		ValueOperations<String, T> operation = redisTemplate.opsForValue();
		return operation.setIfPresent(key, value, timeout, TimeUnit.SECONDS);
	}
	
	/**
	 * 如果键不存在则设置新值，存在则不做任何操作
	 * @param <T>
	 * @param key
	 * @param value
	 * @param timeout
	 * @return true 表示key不存在且已设新值； false 表示key已存在未做任何操作
	 */
	public <T> boolean setIfAbsent(String key, T value, long timeout) {
		ValueOperations<String, T> operation = redisTemplate.opsForValue();
		return operation.setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
	}
	
	/**
	 * 更新value值，过期时间为原key的剩余过期时间
	 * @param <T>
	 * @param key
	 * @param value
	 */
	public <T> void resetValue(String key, T value) {
		Long expire = getExpire(key);
		if(expire != null && expire > 0) {
			set(key, value, getExpire(key));
		}
	}
	
	/**
	 * 递增
	 *
	 * @param key   键
	 * @param delta 要增加几(大于0递增  小于0递减)
	 * @return	递增后的值
	 */
	public long incr(String key, long delta) {
		return redisTemplate.opsForValue().increment(key, delta);
	}
	
	/**
	 * 递增
	 * @param key		键
	 * @param delta		递增值大小
	 * @param timeout	过期时间，必须大于0
	 * @return	递增后的值
	 */
	public long incr(String key, long delta, long timeout) {
		long result = redisTemplate.opsForValue().increment(key, delta);
		if (timeout > 0) {
			expire(key, timeout);
		}
		return result;
	}

	// ================================Map=================================

	/**
	 * HashGet 获取item对应的值
	 *
	 * @param key  键 不能为null
	 * @param item 项 不能为null
	 * @return 值
	 */
	public <T> T hget(String key, String item) {
		HashOperations<String, String, T> operation = redisTemplate.opsForHash();
		return operation.get(key, item);
	}

	/**
	 * 获取key对应的hash表的所有value
	 * @param <T>
	 * @param key
	 * @return
	 */
	public <T> List<T> hmValues(String key) {
		return redisTemplate.opsForHash().values(key);
	}
	
	/**
	 * 获取hashKey对应的所有键值
	 *
	 * @param key 键
	 * @return 对应的多个键值
	 */
	public <T> Map<String, T> hmget(String key) {
		return redisTemplate.opsForHash().entries(key);
	}

	/**
	 * 缓存Map,永不过期
	 *
	 * @param key 键
	 * @param map 对应多个键值 
	 * @param time 时间(秒),time小于等于0永不过期
	 */
	public <T> void hmset(String key, Map<String, T> map, long time) {
		redisTemplate.opsForHash().putAll(key, map);
		if (time > 0) {
			expire(key, time);
		}
	}

	/**
	 * 向一张hash表中放入单项数据,如果不存在将创建
	 *
	 * @param key   键
	 * @param item  项
	 * @param value 值
	 * @param time  时间(秒),time小于等于0永不过期  注意:如果已存在的hash表有时间,这里将会替换原有的时间
	 */
	public <T> void hset(String key, String item, T value, long time) {
		redisTemplate.opsForHash().put(key, item, value);
		if (time > 0) {
			expire(key, time);
		}
	}

	/**
	 * 删除hash表中的值
	 *
	 * @param key  键 不能为null
	 * @param item 项 可以是多个 不能为null
	 */
	public <T> void hdel(String key, T... item) {
		redisTemplate.opsForHash().delete(key, item);
	}

	/**
	 * 判断hash表中是否有该项的值
	 *
	 * @param key  键 不能为null
	 * @param item 项 不能为null
	 * @return true 存在 false不存在
	 */
	public boolean hHasKey(String key, String item) {
		return redisTemplate.opsForHash().hasKey(key, item);
	}

	/**
	 * hash递增 如果不存在,就会创建一个 并把新增后的值返回
	 *
	 * @param key  键
	 * @param item 项
	 * @param delta   要增加几(大于0递增，小于0递减)
	 * @return
	 */
	public double hincr(String key, String item, double delta) {
		return redisTemplate.opsForHash().increment(key, item, delta);
	}

	// ============================Set=============================

	/**
	 * 根据key获取Set中的所有值
	 *
	 * @param key 键
	 * @return
	 */
	public <T> Set<T> sGet(String key) {
		return redisTemplate.opsForSet().members(key);
	}

	/**
	 * 根据value从一个set中查询,是否存在
	 *
	 * @param key   键
	 * @param value 值
	 * @return true 存在 false不存在
	 */
	public <T> boolean sHasKey(String key, T value) {
		return redisTemplate.opsForSet().isMember(key, value);
	}

	/**
	 * 将数据放入set缓存
	 *
	 * @param key    键
	 * @param values 值 可以是多个
	 * @return 成功个数
	 */
	public <T> long sSet(String key, T... values) {
		return redisTemplate.opsForSet().add(key, values);
	}

	/**
	 * 将set数据放入缓存
	 *
	 * @param key    键
	 * @param time   时间(秒)
	 * @param values 值 可以是多个
	 * @return 成功个数
	 */
	public <T> long sSetAndTime(String key, long time, T... values) {
		Long count = redisTemplate.opsForSet().add(key, values);
		if (time > 0) {
			expire(key, time);
		}
		return count;
	}

	/**
	 * 获取set缓存的长度
	 *
	 * @param key 键
	 * @return
	 */
	public long sGetSize(String key) {
		return redisTemplate.opsForSet().size(key);
	}

	/**
	 * 移除值为value的数据
	 *
	 * @param key    键
	 * @param values 值 可以是多个
	 * @return 移除的个数
	 */
	public <T> long setRemove(String key, T... values) {
		return redisTemplate.opsForSet().remove(key, values);
	}

	// ===============================zSet=================================

	/**
	 * 根据key获取zSet中的所有值(正排)
	 *
	 * @param key 键
	 * @param sort 排序方式 ASC正排   DESC倒排，默认正排
	 * @return
	 */
	public <T> Set<T> zGet(String key, String sort) {
		ZSetOperations<String, T> operation = redisTemplate.opsForZSet();
		if("DESC".equals(sort)) {
			return operation.reverseRange(key, 0, -1);
		}
		return operation.range(key, 0, -1);
	}

	/**
	 * 将数据放入set缓存
	 *
	 * @param key    键
	 * @param values 值 可以是多个
	 * @return true 成功 false 失败
	 */
	public <T> void zSet(String key, T value, double score) {
		redisTemplate.opsForZSet().add(key, value, score);
	}
	
	/**
	 * 获取value对应的Score
	 * 
	 * @param <T>
	 * @param key
	 * @param value
	 * @return
	 */
	public <T> Double zGetScore(String key, T value) {
		return redisTemplate.opsForZSet().score(key, value);
	}

	/**
	 * 根据value从一个set中查询,是否存在
	 *
	 * @param key   键
	 * @param value 值
	 * @return true 存在 false不存在
	 */
	public <T> boolean zHasKey(String key, T value) {
		Double score = redisTemplate.opsForZSet().score(key, value);
		return score != null && score >= 0;
	}

	/**
	 * 移除值为value的数据
	 *
	 * @param key    键
	 * @param values 值 可以是多个
	 * @return 移除的个数
	 */
	public <T> long zsetRemove(String key, T... values) {
		return redisTemplate.opsForZSet().remove(key, values);
	}
	
	/**
     * 分页取出zset中的值，不带分数
     * 返回有序的集合，score小的在前面
     * @param key
     * @param offset
     * @param count
     * @return
     */
    public <T> Set<T> zsetPage(String key, int offset, int count) {
    	return redisTemplate.opsForZSet().range(key,  (offset-1)*count, offset*count-1);
    }
    
    /**
     * 分页取出zset中的值，带分数
     * @param key
     * @param offset
     * @param count
     * @return
     */
    public <T> Set<TypedTuple<T>> zsetPageWithScores(String key, long min, long max, int offset, int count) {
    	Set<TypedTuple<T>> result = null;
    	try {
    		result = redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max, (offset-1)*count, count);
    	} catch (Exception e) {
    		log.error("zsetPageWithScores {}", key, e);
    	}
    	return result;
    }

    /**
     * 获取zset中key值对应的数量
     * @param key
     * @return
     */
    public Long zsetSize(String key) {
        try {
            return redisTemplate.opsForZSet().zCard(key);
        } catch (Exception e) {
            log.error("getSize {}", key, e);
        }
        return 0L;
    }

	// ===============================List=================================

	/**
	 * 获取list缓存的内容
	 *
	 * @param key   键
	 * @param start 开始
	 * @param end   结束 0 到 -1代表所有值
	 * @return
	 */
	public <T> List<T> lGet(String key, long start, long end) {
		return redisTemplate.opsForList().range(key, start, end);
	}

	/**
	 * 获取list缓存的长度
	 *
	 * @param key 键
	 * @return
	 */
	public long lGetListSize(String key) {
		return redisTemplate.opsForList().size(key);
	}

	/**
	 * 通过索引 获取list中的值
	 *
	 * @param key   键
	 * @param index 索引 index>=0时， 0 表头，1 第二个元素，依次类推；index<0时，-1，表尾，-2倒数第二个元素，依次类推
	 * @return
	 */
	public <T> T lGetIndex(String key, long index) {
		ListOperations<String, T> operation = redisTemplate.opsForList();
		return operation.index(key, index);
	}

	/**
	 * 将list放入缓存
	 *
	 * @param key   键
	 * @param value 值
	 * @param time  时间(秒)
	 * @return
	 */
	public <T> void lSet(String key, T value, long time) {
		redisTemplate.opsForList().rightPush(key, value);
		if (time > 0) {
			expire(key, time);
		}
	}

	/**
	 * 将list放入缓存
	 *
	 * @param key   键
	 * @param value 值
	 * @param time  时间(秒)
	 * @return
	 */
	public <T> void lSet(String key, List<T> value, long time) {
		redisTemplate.opsForList().rightPushAll(key, value);
		if (time > 0) {
			expire(key, time);
		}
	}

	/**
	 * 向指定长度的列表头部插入一个值，若值已存在先删除再插入到头部
	 * 
	 * @param key
	 * @param value
	 * @param size  指定列表的长度
	 * @return
	 */
	public <T> void lSetLimitSize(String key, T value, long size) {
		ListOperations<String, T> ops = redisTemplate.opsForList();
		// value已存在，先移除
		ops.remove(key, 0, value);
		// 将value插入到列表头部
		ops.leftPush(key, value);
		// 修剪列表，使其只包含指定范围内的元素
		ops.trim(key, 0, size);
	}

	/**
	 * 根据索引修改list中的某条数据
	 *
	 * @param key   键
	 * @param index 索引
	 * @param value 值
	 * @return
	 */

	public <T> void lUpdateIndex(String key, long index, T value) {
		redisTemplate.opsForList().set(key, index, value);
	}

	/**
	 * 移除N个值为value
	 *
	 * @param key   键
	 * @param count 移除多少个
	 * @param value 值
	 * @return 移除的个数
	 */
	public <T> long lRemove(String key, long count, T value) {
		return redisTemplate.opsForList().remove(key, count, value);
	}
	
	/**
	 * 获得缓存的基本对象列表
	 * 
	 * @param pattern 字符串前缀
	 * @return 对象列表
	 */
	public Set<String> keys(String pattern) {
		return redisTemplate.keys(pattern);
	}
	
	/**
	 * 获取匹配的多个key的value
	 * @param <T>
	 * @param pattern 字符串前缀
	 * @return
	 */
	public <T> List<T> multiGet(String pattern) {
		Set<String> keys = redisTemplate.keys(pattern);
		return redisTemplate.opsForValue().multiGet(keys);
	}
	
	/**
	 * 获取匹配的多个key的value，使用了Pipelined
	 * @param <T>
	 * @param pattern 字符串前缀
	 * @return
	 */
	public <T> List<T> multiGetPipelined(String pattern) {
		Set<String> keys = redisTemplate.keys(pattern);
		return redisTemplate.executePipelined(new RedisCallback<T>() {
			@Override
			public T doInRedis(RedisConnection connection) throws DataAccessException {
				 StringRedisConnection conn = (StringRedisConnection)connection;
				for(String key : keys) {
					conn.get(key);
				}
				return null;
			}
		});
	}
	
}